package com.sinosoft.dsp.system.core.vfs;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;

import org.apache.commons.vfs.FileContent;
import org.apache.commons.vfs.FileName;
import org.apache.commons.vfs.FileObject;
import org.apache.commons.vfs.FileSystemException;
import org.apache.commons.vfs.FileSystemManager;
import org.apache.commons.vfs.FileSystemOptions;
import org.apache.commons.vfs.cache.WeakRefFilesCache;
import org.apache.commons.vfs.impl.DefaultFileSystemManager;
import org.apache.commons.vfs.provider.ftp.FtpFileProvider;
import org.apache.commons.vfs.provider.local.DefaultLocalFileProvider;
import org.apache.commons.vfs.provider.local.LocalFile;

import com.sinosoft.dsp.system.core.exception.SinoFileException;
import com.sinosoft.dsp.system.core.i18n.BaseMessages;
import com.sinosoft.dsp.system.core.variables.VariableSpace;
import com.sinosoft.dsp.system.core.vfs.configuration.SinoFileSystemConfigBuilderFactory;
import com.sinosoft.dsp.system.core.vfs.configuration.ISinoFileSystemConfigBuilder;
import com.sinosoft.dsp.system.utils.Const;

/**
 * 基于VFS文件系统的CRUD代理
 * 
 * @author 潘巍（Peter Pan）
 * @since 2011-5-3 下午04:21:27
 */
public class SinoVFSProxy {
	private static final SinoVFSProxy sinoVFSProxy = new SinoVFSProxy();

	private static Class<?> PKG = SinoVFSProxy.class;

	private final DefaultFileSystemManager fsm;

	private SinoVFSProxy() {
		fsm = new DefaultFileSystemManager();
		try {
			FtpFileProvider ftp = new FtpFileProvider();
			DefaultLocalFileProvider lf = new DefaultLocalFileProvider();
			fsm.addProvider("ftp", ftp);
			fsm.addProvider("file", lf);
			fsm.setFilesCache(new WeakRefFilesCache());
			fsm.init();
		} catch (FileSystemException e) {
			e.printStackTrace();
		}

		// 安装shutdown hook，确保文件系统管理器被关闭
		// 清除vfs_cache中的临时文件
		Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
			public void run() {
				if (fsm != null) {
					fsm.close();
				}
			}
		}));
	}

	public FileSystemManager getFileSystemManager() {
		return fsm;
	}

	public static SinoVFSProxy getInstance() {
		return sinoVFSProxy;
	}

	/**
	 * 获得文件名全路径
	 * 
	 * @param vp
	 * @param relativePath
	 * @param uuid
	 * @return
	 */
	public String getFileName(VFSParameters vp, String relativePath, String uuid) {
		return vp.getVFSPrefix() + relativePath + "/" + uuid;
	}

	/**
	 * 
	 * 创建文件
	 * 
	 * @param vp
	 *            VFS参数
	 * @param relativePath
	 *            文件的相对路径，即子目录
	 * @param uuid
	 *            文件所代表实体的ID，即文件名
	 * @param content
	 *            文件内容
	 * @return 返回文件是否创建成功
	 * @throws SinoFileException
	 */

	public boolean createFile(VFSParameters vp, String relativePath,
			String uuid, String content) throws SinoFileException {
		FileObject fileObject = null;
		if (Const.isEmpty(content))
			content = "";
		try {
			String fileName = getFileName(vp, relativePath, uuid);
			fileObject = getFileObject(fileName, null);
			if (fileObject.exists()) {
				throw new SinoFileException("所要创建的文件已经存在，路径：" + relativePath
						+ "，UUID：" + uuid);
			}
			writeFile(fileObject, true, content);
			return true;
		} catch (IOException e) {
			return false;
		} finally {
			closeFileObject(fileObject);
		}
	}

	/**
	 * 判断文件是否存在
	 * 
	 * @param vp
	 *            VFS参数
	 * @param relativePath
	 *            文件的相对路径，即子目录
	 * @param uuid
	 *            文件所代表实体的ID，即文件名
	 * @return 是否存在
	 * @throws SinoFileException
	 */
	public boolean fileExists(VFSParameters vp, String relativePath, String uuid)
			throws SinoFileException {
		FileObject fileObject = null;
		try {
			fileObject = getFileObject(getFileName(vp, relativePath, uuid),
					null);
			return fileObject.exists();
		} catch (IOException e) {
			throw new SinoFileException(e);
		} finally {
			closeFileObject(fileObject);
		}
	}

	public void closeFileObject(FileObject fileObject) {
		if (fileObject != null) {
			try {
				fileObject.close();
			} catch (FileSystemException e) {
				fileObject = null;
			}
		}
	}

	/**
	 * 通过FileObject得到文件名，主要是去除前缀
	 * 
	 * @param fileObject
	 * @return
	 */
	public String getFilename(FileObject fileObject) {
		FileName fileName = fileObject.getName();
		String root = fileName.getRootURI();
		if (!root.startsWith("file:"))return fileName.getURI(); // nothing we can do about non-normal files. //$NON-NLS-1$
		if (root.startsWith("file:////"))
			return fileName.getURI(); // we'll see 4 forward slashes for a
		// windows/smb network share
		if (root.endsWith(":/")) // Windows //$NON-NLS-1$
		{
			root = root.substring(8, 10);
		} else // *nix & OSX
		{
			root = ""; //$NON-NLS-1$
		}
		String fileString = root + fileName.getPath();
		if (!"/".equals(Const.FILE_SEPARATOR)) //$NON-NLS-1$
		{
			fileString = Const.replace(fileString, "/", Const.FILE_SEPARATOR); //$NON-NLS-1$
		}
		return fileString;
	}

	/**
	 * 删除一个文件
	 * 
	 * @param vp
	 * @param relativePath
	 * @param uuid
	 * @return
	 * @throws SinoFileException
	 */
	public boolean deleteFile(VFSParameters vp, String relativePath, String uuid)
			throws SinoFileException {
		FileObject fileObject = null;
		try {
			fileObject = getFileObject(getFileName(vp, relativePath, uuid),
					null);
			fileObject.delete();
			return true;
		} catch (FileSystemException e) {
			return false;
		} finally {
			closeFileObject(fileObject);
		}
	}

	/**
	 * 写入文件
	 * 
	 * @param fileObject
	 *            文件对象
	 * @param append
	 *            是否在现有文件基础上增加
	 * @param content
	 *            写入的内容
	 * @return 是否写入成功
	 * @throws IOException
	 */
	private boolean writeFile(FileObject fileObject, boolean append,
			String content) {
		try {
			OutputStream os = fileObject.getContent().getOutputStream();
			BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));
			bw.write(content);
			bw.flush();
			bw.close();
			return true;
		} catch (IOException e) {
			return false;
		}

	}

	/**
	 * 编辑文件
	 * 
	 * @param vp
	 * @param relativePath
	 * @param uuid
	 * @param content
	 * @return
	 * @throws SinoFileException
	 */
	public boolean editFile(VFSParameters vp, String relativePath, String uuid,
			String content) throws SinoFileException {
		FileObject fileObject = null;
		try {
			// 删除文件
			deleteFile(vp, relativePath, uuid);

			// 创建文件
			String fileName = getFileName(vp, relativePath, uuid);
			do {
				fileObject = getFileObject(fileName, null);
			} while (fileObject.exists());
			writeFile(fileObject, true, content);
			return true;
		} catch (IOException e) {
			return false;
		} finally {
			closeFileObject(fileObject);
		}
	}

	/**
	 * 获得文件对象
	 * 
	 * @param vfsFilename
	 *            文件名称
	 * @param space
	 *            变量空间
	 * @return
	 * @throws SinoFileException
	 */
	public FileObject getFileObject(String vfsFilename, VariableSpace space)
			throws SinoFileException {
		return getFileObject(vfsFilename, space, null);
	}

	public FileObject getFileObject(VFSParameters vp, String relativePath,
			String uuid) throws SinoFileException {
		return getFileObject(getFileName(vp, relativePath, uuid), null);
	}

	/**
	 * 获得文件对象
	 * 
	 * @param vfsFilename
	 *            文件名称
	 * @param space
	 *            变量空间
	 * @param fsOptions
	 *            fs选项
	 * @return
	 * @throws SinoFileException
	 */
	public FileObject getFileObject(String vfsFilename, VariableSpace space,
			FileSystemOptions fsOptions) throws SinoFileException {
		try {
			FileSystemManager fsManager = getFileSystemManager();

			// We have one problem with VFS: if the file is in a subdirectory of
			// the current one: somedir/somefile
			// In that case, VFS doesn't parse the file correctly.
			// We need to put file: in front of it to make it work.
			// However, how are we going to verify this?
			// 
			// We are going to see if the filename starts with one of the known
			// protocols like file: zip: ram: smb: jar: etc.
			// If not, we are going to assume it's a file.
			// 关于VFS有个问题
			boolean relativeFilename = true;
			String[] schemes = fsManager.getSchemes();
			for (int i = 0; i < schemes.length && relativeFilename; i++) {
				if (vfsFilename.startsWith(schemes[i] + ":")) {
					relativeFilename = false;
					// We have a VFS URL, load any options for the file system
					// driver
					fsOptions = buildFsOptions(space, fsOptions, vfsFilename,
							schemes[i]);
				}
			}

			String filename;
			if (vfsFilename.startsWith("\\\\")) {
				File file = new File(vfsFilename);
				filename = file.toURI().toString();
			} else {
				if (relativeFilename) {
					File file = new File(vfsFilename);
					filename = file.getAbsolutePath();
				} else {
					filename = vfsFilename;
				}
			}

			FileObject fileObject = null;

			if (fsOptions != null) {
				fileObject = fsManager.resolveFile(filename, fsOptions);
			} else {
				fileObject = fsManager.resolveFile(filename);
			}

			return fileObject;
		} catch (IOException e) {
			throw new SinoFileException("无法通过文件名获得VFS文件对象'" + vfsFilename
					+ "' : " + e.getMessage());
		}
	}

	public OutputStream getOutputStream(FileObject fileObject, boolean append)
			throws IOException {
		FileObject parent = fileObject.getParent();
		if (parent != null) {
			if (!parent.exists()) {
				throw new IOException(BaseMessages.getString(PKG.getName(),
						"SinoVFS.Exception.ParentDirectoryDoesNotExist",
						getFilename(parent)));
			}
		}
		try {
			fileObject.createFile();
			FileContent content = fileObject.getContent();
			return content.getOutputStream(append);
		} catch (FileSystemException e) {
			// Perhaps if it's a local file, we can retry using the standard
			// File object. This is because on Windows there is a bug in VFS.
			//
			if (fileObject instanceof LocalFile) {
				try {
					String filename = getFilename(fileObject);
					return new FileOutputStream(new File(filename), append);
				} catch (Exception e2) {
					throw e; // throw the original exception: hide the retry.
				}
			} else {
				throw e;
			}
		}
	}

	public OutputStream getOutputStream(String vfsFilename, boolean append)
			throws SinoFileException {
		return getOutputStream(vfsFilename, null, append);
	}

	public OutputStream getOutputStream(String vfsFilename,
			VariableSpace space, boolean append) throws SinoFileException {
		try {
			FileObject fileObject = getFileObject(vfsFilename, space);
			return getOutputStream(fileObject, append);
		} catch (IOException e) {
			throw new SinoFileException(e);
		}
	}

	private FileSystemOptions buildFsOptions(VariableSpace varSpace,
			FileSystemOptions sourceOptions, String vfsFilename, String scheme)
			throws IOException {
		if (varSpace == null || vfsFilename == null) {
			// We cannot extract settings from a non-existant variable space
			return null;
		}

		ISinoFileSystemConfigBuilder configBuilder = SinoFileSystemConfigBuilderFactory
				.getConfigBuilder(varSpace, scheme);

		FileSystemOptions fsOptions = (sourceOptions == null) ? new FileSystemOptions()
				: sourceOptions;

		String[] varList = varSpace.listVariables();

		for (String var : varList) {
			if (var.startsWith("vfs.")) { //$NON-NLS-1$
				String param = configBuilder.parseParameterName(var, scheme);
				if (param != null) {
					configBuilder.setParameter(fsOptions, param, varSpace
							.getVariable(var), var, vfsFilename);
				} else {
					throw new IOException(
							"FileSystemConfigBuilder could not parse parameter: " + var); //$NON-NLS-1$
				}
			}
		}
		return fsOptions;
	}

	public InputStream getInputStream(FileObject fileObject)
			throws FileSystemException {
		FileContent content = fileObject.getContent();
		return content.getInputStream();
	}

	/**
	 * 得到指定路径的文件流
	 * 
	 * @param vfsFilename
	 * @param space
	 * @return
	 * @throws SinoFileException
	 */
	public InputStream getInputStream(String vfsFilename, VariableSpace space)
			throws SinoFileException {
		try {
			FileObject fileObject = getFileObject(vfsFilename, space);
			return getInputStream(fileObject);
		} catch (IOException e) {
			throw new SinoFileException(e);
		}
	}

	/**
	 * 读取文件
	 * 
	 * @param vp
	 * @param relativePath
	 * @param uuid
	 * @return
	 * @throws SinoFileException
	 */
	public InputStream readFile(VFSParameters vp, String relativePath,
			String uuid) throws SinoFileException {
		try {
			FileObject fileObject = getFileObject(getFileName(vp, relativePath,
					uuid), null);
			return getInputStream(fileObject);
		} catch (IOException e) {
			throw new SinoFileException(e);
		}
	}

}
